﻿
using System;
using System.IO;
using System.Diagnostics;

namespace LExperiment.NetworkLib
{
    // ------------------------------------------------------- //
    #region [ packet header ]

    public sealed class PacketHeader
    {
        // ********************************* //
        #region [ member variables ]

        public const int HeaderLength = 6;

        private ushort m_cmd;
        private int m_bodyLength;

        #endregion

        // ********************************* //
        public PacketHeader() { }

        public PacketHeader(ushort cmd, int bodyLength)
        {
            m_cmd = cmd;
            m_bodyLength = bodyLength;
        }

        public PacketHeader(PacketHeader srcHeader)
        {
            m_cmd = srcHeader.m_cmd;
            m_bodyLength = srcHeader.m_bodyLength;
        }

        public ushort Cmd { get { return m_cmd; } set { m_cmd = value; } }

        public int BodyLength
        {
            get { return m_bodyLength; }
            set { m_bodyLength = value; }
        }

        // ********************************* //
        #region [ marshal and unmarshal ]

        public byte[] BinaryImage
        {
            get
            {
                byte[] buffer = new byte[HeaderLength];
                BufferUtility.CopyBytes(m_cmd, buffer, 0);
                BufferUtility.CopyBytes(m_bodyLength, buffer, 2);
                return buffer;
            }
        }

        public void UnMarshal(byte[] buffer, int offset)
        {
            m_cmd = BufferUtility.ToUInt16(buffer, offset);
            m_bodyLength = BufferUtility.ToInt32(buffer, offset + 2);
        }

        #endregion

        // ********************************* //
        #region [ override object virtual methods ]

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (object.ReferenceEquals(this, obj))
                return true;

            if (obj.GetType() != typeof(PacketHeader))
                return false;

            PacketHeader otherheader = (PacketHeader)obj;
            return (m_cmd == otherheader.m_cmd && m_bodyLength == otherheader.m_bodyLength);
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override string ToString()
        {
            return string.Format("{0}-{1}", m_cmd, m_bodyLength);
        }

        #endregion
    }

    #endregion

    // ------------------------------------------------------- //
    #region [ stream FSM ]

    public enum FsmType
    {
        HeaderFsm,
        BodyFsm
    }

    public interface IFsmParser
    {
        Stream InputStream { get; }

        void OnIOException(IOException ex);
        void OnReachStreamEnd();
        void OnHeaderCompleted(PacketHeader header);
        void OnBodyCompleted();
    }

    public abstract class FsmBase
    {
        protected IFsmParser m_parent;
        protected byte[] m_buffer;
        protected volatile int m_bytesRead;// put keyword "volatile" to enhance thread safety
        protected Stream m_inputStream;

        public FsmBase(IFsmParser parent)
        {
            m_parent = parent;
            m_bytesRead = 0;
            m_inputStream = parent.InputStream;
        }

        public byte[] Buffer { get { return m_buffer; } }
        public int BytesRead { get { return m_bytesRead; } }

        public void BeginRead()
        {
            m_inputStream.BeginRead(m_buffer, m_bytesRead, this.WholeLength - m_bytesRead, this.ReadCallback, null);
        }

        protected void ReadCallback(IAsyncResult asyncResult)
        {
            int chunkSize = 0;
            try
            {
                chunkSize = m_inputStream.EndRead(asyncResult);
                m_bytesRead += chunkSize;
            }
            catch (IOException ex)
            {
                m_parent.OnIOException(ex);
                return;
            }

            if (chunkSize == 0)
            {
                m_parent.OnReachStreamEnd();
            }
            else if (m_bytesRead < this.WholeLength)
            {
                BeginRead();
            }
            else
            {
                Debug.Assert(m_bytesRead == this.WholeLength);
                // reset for next read, in "OnReadCompleted" maybe set a flag to start or resume another thread
                // so we must make everything ready before another thread runs
                // so we must reset "m_bytesRead" before "OnReadCompleted"
                m_bytesRead = 0;
                OnReadCompleted();
            }
        }

        public abstract FsmType FsmType { get; }
        public abstract int WholeLength { get; set; }
        protected abstract void OnReadCompleted();
    }

    public sealed class HeaderFsm : FsmBase
    {
        private PacketHeader m_header;

        public HeaderFsm(IFsmParser parent)
            : base(parent)
        {
            m_header = new PacketHeader();
            m_buffer = new byte[PacketHeader.HeaderLength];
        }

        public override FsmType FsmType
        {
            get { return FsmType.HeaderFsm; }
        }

        public override int WholeLength
        {
            get { return PacketHeader.HeaderLength; }
            set { throw new NotImplementedException("should not be invoked"); }
        }

        protected override void OnReadCompleted()
        {
            m_header.UnMarshal(m_buffer, 0);
            m_parent.OnHeaderCompleted(m_header);
        }
    }

    public sealed class BodyFsm : FsmBase
    {
        public BodyFsm(IFsmParser parent)
            : base(parent)
        { }

        public override FsmType FsmType
        {
            get { return FsmType.BodyFsm; }
        }

        public override int WholeLength
        {
            get { return m_buffer.Length; }
            set
            {
                if (value < 0)
                    throw new ArgumentException("body length should be positive");

                if (m_buffer == null || value != m_buffer.Length)
                {
                    m_buffer = new byte[value];
                }
                m_bytesRead = 0;
            }
        }

        protected override void OnReadCompleted()
        {
            m_parent.OnBodyCompleted();
        }
    }

    // ------------------------------------------------------- //
    #region [ event argument definition ]

    public sealed class BodyRecvEventArgs : EventArgs
    {
        private PacketHeader m_header;
        private byte[] m_body;

        public BodyRecvEventArgs(PacketHeader header, byte[] body)
        {
            m_header = header;
            m_body = body;
        }

        public PacketHeader Header { get { return m_header; } }
        public byte[] Body { get { return m_body; } }
    }

    public sealed class OnlyHeaderRecvEventArgs : EventArgs
    {
        private PacketHeader m_header;

        public OnlyHeaderRecvEventArgs(PacketHeader header) { m_header = header; }
        public PacketHeader Header { get { return m_header; } }
    }

    public sealed class IoExceptionEventArgs : EventArgs
    {
        private IOException m_exception;

        public IoExceptionEventArgs(IOException ex) { m_exception = ex; }
        public IOException Exception { get { return m_exception; } }
    }

    #endregion

    // ------------------------------------------------------- //
    public sealed class HeaderBodyFsmParser : IFsmParser
    {
        //**************************************//
        #region [ member variables ]

        public event EventHandler StreamEndEvtHandler;
        public event EventHandler<BodyRecvEventArgs> BodyRecvedEvtHandler;
        public event EventHandler<OnlyHeaderRecvEventArgs> OnlyHeaderRecvEvtHandler;
        public event EventHandler<IoExceptionEventArgs> IoExceptionEvtHandler;

        private Stream m_stream;
        private PacketHeader m_header;

        private FsmBase m_currentFsm;
        private HeaderFsm m_headerFsm;
        private BodyFsm m_bodyFsm;

        #endregion

        //**************************************//

        public HeaderBodyFsmParser(Stream stream)
        {
            m_stream = stream;
            m_headerFsm = new HeaderFsm(this);
            m_bodyFsm = new BodyFsm(this);
            m_currentFsm = m_headerFsm;
        }

        public void BeginRead()
        {
            m_currentFsm.BeginRead();
        }

        //**************************************//
        #region [ implement IFsmParser ]

        public Stream InputStream
        {
            get { return m_stream; }
        }

        public void OnIOException(IOException ex)
        {
            if (IoExceptionEvtHandler != null)
                IoExceptionEvtHandler(this, new IoExceptionEventArgs(ex));
        }

        public void OnReachStreamEnd()
        {
            if (StreamEndEvtHandler != null)
                StreamEndEvtHandler(this, EventArgs.Empty);
        }

        public void OnHeaderCompleted(PacketHeader header)
        {
            m_header = header;

            if (m_header.BodyLength > 0)
            {
                m_bodyFsm.WholeLength = m_header.BodyLength;
                m_currentFsm = m_bodyFsm;
            }
            else
            {
                m_currentFsm = m_headerFsm;

                if (OnlyHeaderRecvEvtHandler != null)
                    OnlyHeaderRecvEvtHandler(this, new OnlyHeaderRecvEventArgs(m_header));
            }

            BeginRead();
        }

        public void OnBodyCompleted()
        {
            m_currentFsm = m_headerFsm;

            // !!!!!!!!!!!!!!!!!!!! the sequence is very important
            // !!!!!!!!!!!!!!!!!!!! first deal with the current header and body, and then post another asynchronous read 
            // !!!!!!!!!!!!!!!!!!!! this means that when another new asynchronous read is post, the header binImage and body body
            // !!!!!!!!!!!!!!!!!!!! have already been read, then eligible for writting
            // !!!!!!!!!!!!!!!!!!!! if we switch the order, first post another asynchronous read, and then process the received header
            // !!!!!!!!!!!!!!!!!!!! and body, then it may cause race condition to the binImage
            // !!!!!!!!!!!!!!!!!!!! if we want a higher concurrency, we must first copy the header and body image, then post another
            // !!!!!!!!!!!!!!!!!!!! asynchronous read, and then deal with the copy header and copy body image, to avoid race condition
            if (BodyRecvedEvtHandler != null)
                BodyRecvedEvtHandler(this, new BodyRecvEventArgs(m_header, m_bodyFsm.Buffer));

            BeginRead();
        }

        #endregion
    }

    #endregion
}